/*-------------------------------------------------------------------------
 * drawElements Quality Program OpenGL ES 3.0 Module
 * -------------------------------------------------
 *
 * Copyright 2014 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 *//*!
 * \file
 * \brief Instanced rendering tests.
 *//*--------------------------------------------------------------------*/

#include "es3fInstancedRenderingTests.hpp"
#include "gluPixelTransfer.hpp"
#include "gluShaderProgram.hpp"
#include "gluShaderUtil.hpp"
#include "tcuTestLog.hpp"
#include "tcuSurface.hpp"
#include "tcuImageCompare.hpp"
#include "tcuVector.hpp"
#include "tcuRenderTarget.hpp"
#include "deRandom.hpp"
#include "deStringUtil.hpp"
#include "deString.h"

#include "glw.h"

using std::vector;
using std::string;

namespace deqp
{
namespace gles3
{
namespace Functional
{

static const int	MAX_RENDER_WIDTH		= 128;
static const int	MAX_RENDER_HEIGHT		= 128;

static const int	QUAD_GRID_SIZE			= 127;

// Attribute divisors for the attributes defining the color's RGB components.
static const int	ATTRIB_DIVISOR_R		= 3;
static const int	ATTRIB_DIVISOR_G		= 2;
static const int	ATTRIB_DIVISOR_B		= 1;

static const int	OFFSET_COMPONENTS		= 3; // \note Affects whether a float or a vecN is used in shader, but only first component is non-zero.

// Scale and bias values when converting float to integer, when attribute is of integer type.
static const float	FLOAT_INT_SCALE			= 100.0f;
static const float	FLOAT_INT_BIAS			= -50.0f;
static const float	FLOAT_UINT_SCALE		= 100.0f;
static const float	FLOAT_UINT_BIAS			= 0.0f;

// \note Non-anonymous namespace needed; VarComp is used as a template parameter.
namespace vcns
{

union VarComp
{
	float		f32;
	deUint32	u32;
	deInt32		i32;

	VarComp(float v)	: f32(v) {}
	VarComp(deUint32 v)	: u32(v) {}
	VarComp(deInt32 v)	: i32(v) {}
};
DE_STATIC_ASSERT(sizeof(VarComp) == sizeof(deUint32));

} // vcns

using namespace vcns;

class InstancedRenderingCase : public TestCase
{
public:
	enum DrawFunction
	{
		FUNCTION_DRAW_ARRAYS_INSTANCED = 0,
		FUNCTION_DRAW_ELEMENTS_INSTANCED,

		FUNCTION_LAST
	};

	enum InstancingType
	{
		TYPE_INSTANCE_ID = 0,
		TYPE_ATTRIB_DIVISOR,
		TYPE_MIXED,

		TYPE_LAST
	};

								InstancedRenderingCase	(Context& context, const char* name, const char* description, DrawFunction function, InstancingType instancingType, glu::DataType rgbAttrType, int numInstances);
								~InstancedRenderingCase	(void);

	void						init					(void);
	void						deinit					(void);
	IterateResult				iterate					(void);

private:
								InstancedRenderingCase	(const InstancedRenderingCase& other);
	InstancedRenderingCase&		operator=				(const InstancedRenderingCase& other);

	void						pushVarCompAttrib		(vector<VarComp>& vec, float val);

	void						setupVarAttribPointer	(const void* attrPtr, int startLocation, int divisor);
	void						setupAndRender			(void);
	void						computeReference		(tcu::Surface& dst);

	DrawFunction				m_function;
	InstancingType				m_instancingType;
	glu::DataType				m_rgbAttrType;			// \note Instance attribute types, color components only. Position offset attribute is always float/vecN.
	int							m_numInstances;

	vector<float>				m_gridVertexPositions;	// X and Y components per vertex.
	vector<deUint16>			m_gridIndices;			// \note Only used if m_function is FUNCTION_DRAW_ELEMENTS_INSTANCED.

	// \note Some or all of the following instance attribute parameters may be unused with TYPE_INSTANCE_ID or TYPE_MIXED.
	vector<float>				m_instanceOffsets;		// Position offsets. OFFSET_COMPONENTS components per offset.
	// Attribute data for float, int or uint (or respective vector types) color components.
	vector<VarComp>				m_instanceColorR;
	vector<VarComp>				m_instanceColorG;
	vector<VarComp>				m_instanceColorB;

	glu::ShaderProgram*			m_program;
};

InstancedRenderingCase::InstancedRenderingCase (Context& context, const char* name, const char* description, DrawFunction function, InstancingType instancingType, glu::DataType rgbAttrType, int numInstances)
	: TestCase			(context, name, description)
	, m_function		(function)
	, m_instancingType	(instancingType)
	, m_rgbAttrType		(rgbAttrType)
	, m_numInstances	(numInstances)
	, m_program			(DE_NULL)
{
}

InstancedRenderingCase::~InstancedRenderingCase (void)
{
	InstancedRenderingCase::deinit();
}

// Helper function that does biasing and scaling when converting float to integer.
void InstancedRenderingCase::pushVarCompAttrib (vector<VarComp>& vec, float val)
{
	bool	isFloatCase	= glu::isDataTypeFloatOrVec(m_rgbAttrType);
	bool	isIntCase	= glu::isDataTypeIntOrIVec(m_rgbAttrType);
	bool	isUintCase	= glu::isDataTypeUintOrUVec(m_rgbAttrType);
	bool	isMatCase	= glu::isDataTypeMatrix(m_rgbAttrType);

	if (isFloatCase || isMatCase)
		vec.push_back(VarComp(val));
	else if (isIntCase)
		vec.push_back(VarComp((deInt32)(val*FLOAT_INT_SCALE + FLOAT_INT_BIAS)));
	else if (isUintCase)
		vec.push_back(VarComp((deUint32)(val*FLOAT_UINT_SCALE + FLOAT_UINT_BIAS)));
	else
		DE_ASSERT(DE_FALSE);
}

void InstancedRenderingCase::init (void)
{
	bool	isFloatCase			= glu::isDataTypeFloatOrVec(m_rgbAttrType);
	bool	isIntCase			= glu::isDataTypeIntOrIVec(m_rgbAttrType);
	bool	isUintCase			= glu::isDataTypeUintOrUVec(m_rgbAttrType);
	bool	isMatCase			= glu::isDataTypeMatrix(m_rgbAttrType);
	int		typeSize			= glu::getDataTypeScalarSize(m_rgbAttrType);
	bool	isScalarCase		= typeSize == 1;
	string	swizzleFirst		= isScalarCase ? "" : ".x";
	string	typeName			= glu::getDataTypeName(m_rgbAttrType);

	string	floatIntScaleStr	= "(" + de::floatToString(FLOAT_INT_SCALE, 3) + ")";
	string	floatIntBiasStr		= "(" + de::floatToString(FLOAT_INT_BIAS, 3) + ")";
	string	floatUintScaleStr	= "(" + de::floatToString(FLOAT_UINT_SCALE, 3) + ")";
	string	floatUintBiasStr	= "(" + de::floatToString(FLOAT_UINT_BIAS, 3) + ")";

	DE_ASSERT(isFloatCase || isIntCase || isUintCase || isMatCase);

	// Generate shader.
	// \note For case TYPE_MIXED, vertex position offset and color red component get their values from instance id, while green and blue get their values from instanced attributes.

	string numInstancesStr = de::toString(m_numInstances) + ".0";

	string instanceAttribs;
	string posExpression;
	string colorRExpression;
	string colorGExpression;
	string colorBExpression;

	if (m_instancingType == TYPE_INSTANCE_ID || m_instancingType == TYPE_MIXED)
	{
		posExpression = "a_position + vec4(float(gl_InstanceID) * 2.0 / " + numInstancesStr + ", 0.0, 0.0, 0.0)";
		colorRExpression = "float(gl_InstanceID)/" + numInstancesStr;

		if (m_instancingType == TYPE_INSTANCE_ID)
		{
			colorGExpression = "float(gl_InstanceID)*2.0/" + numInstancesStr;
			colorBExpression = "1.0 - float(gl_InstanceID)/" + numInstancesStr;
		}
	}

	if (m_instancingType == TYPE_ATTRIB_DIVISOR || m_instancingType == TYPE_MIXED)
	{
		if (m_instancingType == TYPE_ATTRIB_DIVISOR)
		{
			posExpression = "a_position + vec4(a_instanceOffset";

			DE_STATIC_ASSERT(OFFSET_COMPONENTS >= 1 && OFFSET_COMPONENTS <= 4);

			for (int i = 0; i < 4-OFFSET_COMPONENTS; i++)
				posExpression += ", 0.0";
			posExpression += ")";

			if (isFloatCase)
				colorRExpression = "a_instanceR" + swizzleFirst;
			else if (isIntCase)
				colorRExpression = "(float(a_instanceR" + swizzleFirst + ") - " + floatIntBiasStr + ") / " + floatIntScaleStr;
			else if (isUintCase)
				colorRExpression = "(float(a_instanceR" + swizzleFirst + ") - " + floatUintBiasStr + ") / " + floatUintScaleStr;
			else if (isMatCase)
				colorRExpression = "a_instanceR[0][0]";
			else
				DE_ASSERT(DE_FALSE);

			instanceAttribs += "in highp " + (OFFSET_COMPONENTS == 1 ? string("float") : "vec" + de::toString(OFFSET_COMPONENTS)) + " a_instanceOffset;\n";
			instanceAttribs += "in mediump " + typeName + " a_instanceR;\n";
		}

		if (isFloatCase)
		{
			colorGExpression = "a_instanceG" + swizzleFirst;
			colorBExpression = "a_instanceB" + swizzleFirst;
		}
		else if (isIntCase)
		{
			colorGExpression = "(float(a_instanceG" + swizzleFirst + ") - " + floatIntBiasStr + ") / " + floatIntScaleStr;
			colorBExpression = "(float(a_instanceB" + swizzleFirst + ") - " + floatIntBiasStr + ") / " + floatIntScaleStr;
		}
		else if (isUintCase)
		{
			colorGExpression = "(float(a_instanceG" + swizzleFirst + ") - " + floatUintBiasStr + ") / " + floatUintScaleStr;
			colorBExpression = "(float(a_instanceB" + swizzleFirst + ") - " + floatUintBiasStr + ") / " + floatUintScaleStr;
		}
		else if (isMatCase)
		{
			colorGExpression = "a_instanceG[0][0]";
			colorBExpression = "a_instanceB[0][0]";
		}
		else
			DE_ASSERT(DE_FALSE);

		instanceAttribs += "in mediump " + typeName + " a_instanceG;\n";
		instanceAttribs += "in mediump " + typeName + " a_instanceB;\n";
	}

	DE_ASSERT(!posExpression.empty());
	DE_ASSERT(!colorRExpression.empty());
	DE_ASSERT(!colorGExpression.empty());
	DE_ASSERT(!colorBExpression.empty());

	std::string vertShaderSourceStr =
		"#version 300 es\n"
		"in highp vec4 a_position;\n" +
		instanceAttribs +
		"out mediump vec4 v_color;\n"
		"\n"
		"void main()\n"
		"{\n"
		"	gl_Position = " + posExpression + ";\n"
		"	v_color.r = " + colorRExpression + ";\n"
		"	v_color.g = " + colorGExpression + ";\n"
		"	v_color.b = " + colorBExpression + ";\n"
		"	v_color.a = 1.0;\n"
		"}\n";

	static const char* fragShaderSource =
		"#version 300 es\n"
		"layout(location = 0) out mediump vec4 o_color;\n"
		"in mediump vec4 v_color;\n"
		"\n"
		"void main()\n"
		"{\n"
		"	o_color = v_color;\n"
		"}\n";

	// Create shader program and log it.

	DE_ASSERT(!m_program);
	m_program = new glu::ShaderProgram(m_context.getRenderContext(), glu::makeVtxFragSources(vertShaderSourceStr, fragShaderSource));

	tcu::TestLog& log = m_testCtx.getLog();

	log << *m_program;

	if(!m_program->isOk())
		TCU_FAIL("Failed to compile shader");

	// Vertex shader attributes.

	if (m_function == FUNCTION_DRAW_ELEMENTS_INSTANCED)
	{
		// Vertex positions. Positions form a vertical bar of width <screen width>/<number of instances>.

		for (int y = 0; y < QUAD_GRID_SIZE + 1; y++)
			for (int x = 0; x < QUAD_GRID_SIZE + 1; x++)
			{
				float fx = -1.0f + (float)x / (float)QUAD_GRID_SIZE * 2.0f / (float)m_numInstances;
				float fy = -1.0f + (float)y / (float)QUAD_GRID_SIZE * 2.0f;

				m_gridVertexPositions.push_back(fx);
				m_gridVertexPositions.push_back(fy);
			}

		// Indices.

		for (int y = 0; y < QUAD_GRID_SIZE; y++)
			for (int x = 0; x < QUAD_GRID_SIZE; x++)
			{
				int ndx00 = y*(QUAD_GRID_SIZE + 1) + x;
				int ndx10 = y*(QUAD_GRID_SIZE + 1) + x + 1;
				int ndx01 = (y + 1)*(QUAD_GRID_SIZE + 1) + x;
				int ndx11 = (y + 1)*(QUAD_GRID_SIZE + 1) + x + 1;

				// Lower-left triangle of a quad.
				m_gridIndices.push_back((deUint16)ndx00);
				m_gridIndices.push_back((deUint16)ndx10);
				m_gridIndices.push_back((deUint16)ndx01);

				// Upper-right triangle of a quad.
				m_gridIndices.push_back((deUint16)ndx11);
				m_gridIndices.push_back((deUint16)ndx01);
				m_gridIndices.push_back((deUint16)ndx10);
			}
	}
	else
	{
		DE_ASSERT(m_function == FUNCTION_DRAW_ARRAYS_INSTANCED);

		// Vertex positions. Positions form a vertical bar of width <screen width>/<number of instances>.

		for (int y = 0; y < QUAD_GRID_SIZE; y++)
			for (int x = 0; x < QUAD_GRID_SIZE; x++)
			{
				float fx0 = -1.0f + (float)(x+0) / (float)QUAD_GRID_SIZE * 2.0f / (float)m_numInstances;
				float fx1 = -1.0f + (float)(x+1) / (float)QUAD_GRID_SIZE * 2.0f / (float)m_numInstances;
				float fy0 = -1.0f + (float)(y+0) / (float)QUAD_GRID_SIZE * 2.0f;
				float fy1 = -1.0f + (float)(y+1) / (float)QUAD_GRID_SIZE * 2.0f;

				// Vertices of a quad's lower-left triangle: (fx0, fy0), (fx1, fy0) and (fx0, fy1)
				m_gridVertexPositions.push_back(fx0);
				m_gridVertexPositions.push_back(fy0);
				m_gridVertexPositions.push_back(fx1);
				m_gridVertexPositions.push_back(fy0);
				m_gridVertexPositions.push_back(fx0);
				m_gridVertexPositions.push_back(fy1);

				// Vertices of a quad's upper-right triangle: (fx1, fy1), (fx0, fy1) and (fx1, fy0)
				m_gridVertexPositions.push_back(fx1);
				m_gridVertexPositions.push_back(fy1);
				m_gridVertexPositions.push_back(fx0);
				m_gridVertexPositions.push_back(fy1);
				m_gridVertexPositions.push_back(fx1);
				m_gridVertexPositions.push_back(fy0);
			}
	}

	// Instanced attributes: position offset and color RGB components.

	if (m_instancingType == TYPE_ATTRIB_DIVISOR || m_instancingType == TYPE_MIXED)
	{
		if (m_instancingType == TYPE_ATTRIB_DIVISOR)
		{
			// Offsets are such that the vertical bars are drawn next to each other.
			for (int i = 0; i < m_numInstances; i++)
			{
				m_instanceOffsets.push_back((float)i * 2.0f / (float)m_numInstances);

				DE_STATIC_ASSERT(OFFSET_COMPONENTS >= 1 && OFFSET_COMPONENTS <= 4);

				for (int j = 0; j < OFFSET_COMPONENTS-1; j++)
					m_instanceOffsets.push_back(0.0f);
			}

			int rInstances = m_numInstances / ATTRIB_DIVISOR_R + (m_numInstances % ATTRIB_DIVISOR_R == 0 ? 0 : 1);
			for (int i = 0; i < rInstances; i++)
			{
				pushVarCompAttrib(m_instanceColorR, (float)i / (float)rInstances);

				for (int j = 0; j < typeSize - 1; j++)
					pushVarCompAttrib(m_instanceColorR, 0.0f);
			}
		}

		int gInstances = m_numInstances / ATTRIB_DIVISOR_G + (m_numInstances % ATTRIB_DIVISOR_G == 0 ? 0 : 1);
		for (int i = 0; i < gInstances; i++)
		{
			pushVarCompAttrib(m_instanceColorG, (float)i*2.0f / (float)gInstances);

			for (int j = 0; j < typeSize - 1; j++)
				pushVarCompAttrib(m_instanceColorG, 0.0f);
		}

		int bInstances = m_numInstances / ATTRIB_DIVISOR_B + (m_numInstances % ATTRIB_DIVISOR_B == 0 ? 0 : 1);
		for (int i = 0; i < bInstances; i++)
		{
			pushVarCompAttrib(m_instanceColorB, 1.0f - (float)i / (float)bInstances);

			for (int j = 0; j < typeSize - 1; j++)
				pushVarCompAttrib(m_instanceColorB, 0.0f);
		}
	}
}

void InstancedRenderingCase::deinit (void)
{
	delete m_program;
	m_program = DE_NULL;
}

InstancedRenderingCase::IterateResult InstancedRenderingCase::iterate (void)
{
	int							width			= deMin32(m_context.getRenderTarget().getWidth(), MAX_RENDER_WIDTH);
	int							height			= deMin32(m_context.getRenderTarget().getHeight(), MAX_RENDER_HEIGHT);

	int							xOffsetMax		= m_context.getRenderTarget().getWidth() - width;
	int							yOffsetMax		= m_context.getRenderTarget().getHeight() - height;

	de::Random					rnd				(deStringHash(getName()));

	int							xOffset			= rnd.getInt(0, xOffsetMax);
	int							yOffset			= rnd.getInt(0, yOffsetMax);
	tcu::Surface				referenceImg	(width, height);
	tcu::Surface				resultImg		(width, height);

	// Draw result.

	glViewport(xOffset, yOffset, width, height);

	setupAndRender();

	glu::readPixels(m_context.getRenderContext(), xOffset, yOffset, resultImg.getAccess());

	// Compute reference.

	computeReference(referenceImg);

	// Compare.

	bool testOk = tcu::fuzzyCompare(m_testCtx.getLog(), "ComparisonResult", "Image comparison result", referenceImg, resultImg, 0.05f, tcu::COMPARE_LOG_RESULT);

	m_testCtx.setTestResult(testOk ? QP_TEST_RESULT_PASS	: QP_TEST_RESULT_FAIL,
							testOk ? "Pass"					: "Fail");

	return STOP;
}

void InstancedRenderingCase::setupVarAttribPointer (const void* attrPtr, int location, int divisor)
{
	bool	isFloatCase		= glu::isDataTypeFloatOrVec(m_rgbAttrType);
	bool	isIntCase		= glu::isDataTypeIntOrIVec(m_rgbAttrType);
	bool	isUintCase		= glu::isDataTypeUintOrUVec(m_rgbAttrType);
	bool	isMatCase		= glu::isDataTypeMatrix(m_rgbAttrType);
	int		typeSize		= glu::getDataTypeScalarSize(m_rgbAttrType);
	int		numSlots		= isMatCase ? glu::getDataTypeMatrixNumColumns(m_rgbAttrType) : 1; // Matrix uses as many attribute slots as it has columns.

	for (int slotNdx = 0; slotNdx < numSlots; slotNdx++)
	{
		int curLoc = location + slotNdx;

		glEnableVertexAttribArray(curLoc);
		glVertexAttribDivisor(curLoc, divisor);

		if (isFloatCase)
			glVertexAttribPointer(curLoc, typeSize, GL_FLOAT, GL_FALSE, 0, attrPtr);
		else if (isIntCase)
			glVertexAttribIPointer(curLoc, typeSize, GL_INT, 0, attrPtr);
		else if (isUintCase)
			glVertexAttribIPointer(curLoc, typeSize, GL_UNSIGNED_INT, 0, attrPtr);
		else if (isMatCase)
		{
			int numRows = glu::getDataTypeMatrixNumRows(m_rgbAttrType);
			int numCols = glu::getDataTypeMatrixNumColumns(m_rgbAttrType);

			glVertexAttribPointer(curLoc, numRows, GL_FLOAT, GL_FALSE, numCols*numRows*(int)sizeof(float), attrPtr);
		}
		else
			DE_ASSERT(DE_FALSE);
	}
}

void InstancedRenderingCase::setupAndRender (void)
{
	deUint32 program = m_program->getProgram();

	glUseProgram(program);

	{
		// Setup attributes.

		// Position attribute is non-instanced.
		int positionLoc = glGetAttribLocation(program, "a_position");
		glEnableVertexAttribArray(positionLoc);
		glVertexAttribPointer(positionLoc, 2, GL_FLOAT, GL_FALSE, 0, &m_gridVertexPositions[0]);

		if (m_instancingType == TYPE_ATTRIB_DIVISOR || m_instancingType == TYPE_MIXED)
		{
			if (m_instancingType == TYPE_ATTRIB_DIVISOR)
			{
				// Position offset attribute is instanced with separate offset for every instance.
				int offsetLoc = glGetAttribLocation(program, "a_instanceOffset");
				glEnableVertexAttribArray(offsetLoc);
				glVertexAttribDivisor(offsetLoc, 1);
				glVertexAttribPointer(offsetLoc, OFFSET_COMPONENTS, GL_FLOAT, GL_FALSE, 0, &m_instanceOffsets[0]);

				int rLoc = glGetAttribLocation(program, "a_instanceR");
				setupVarAttribPointer((void*)&m_instanceColorR[0].u32, rLoc, ATTRIB_DIVISOR_R);
			}

			int gLoc = glGetAttribLocation(program, "a_instanceG");
			setupVarAttribPointer((void*)&m_instanceColorG[0].u32, gLoc, ATTRIB_DIVISOR_G);

			int bLoc = glGetAttribLocation(program, "a_instanceB");
			setupVarAttribPointer((void*)&m_instanceColorB[0].u32, bLoc, ATTRIB_DIVISOR_B);
		}
	}

	// Draw using appropriate function.

	if (m_function == FUNCTION_DRAW_ARRAYS_INSTANCED)
	{
		const int numPositionComponents = 2;
		glDrawArraysInstanced(GL_TRIANGLES, 0, ((int)m_gridVertexPositions.size() / numPositionComponents), m_numInstances);
	}
	else
		glDrawElementsInstanced(GL_TRIANGLES, (int)m_gridIndices.size(), GL_UNSIGNED_SHORT, &m_gridIndices[0], m_numInstances);

	glUseProgram(0);
}

void InstancedRenderingCase::computeReference (tcu::Surface& dst)
{
	int wid = dst.getWidth();
	int hei = dst.getHeight();

	// Draw a rectangle (vertical bar) for each instance.

	for (int instanceNdx = 0; instanceNdx < m_numInstances; instanceNdx++)
	{
		int xStart		= instanceNdx * wid / m_numInstances;
		int xEnd		= (instanceNdx + 1) * wid / m_numInstances;

		// Emulate attribute divisors if that is the case.

		int clrNdxR		= m_instancingType == TYPE_ATTRIB_DIVISOR									? instanceNdx / ATTRIB_DIVISOR_R : instanceNdx;
		int clrNdxG		= m_instancingType == TYPE_ATTRIB_DIVISOR || m_instancingType == TYPE_MIXED	? instanceNdx / ATTRIB_DIVISOR_G : instanceNdx;
		int clrNdxB		= m_instancingType == TYPE_ATTRIB_DIVISOR || m_instancingType == TYPE_MIXED	? instanceNdx / ATTRIB_DIVISOR_B : instanceNdx;

		int rInstances	= m_instancingType == TYPE_ATTRIB_DIVISOR									? m_numInstances / ATTRIB_DIVISOR_R + (m_numInstances % ATTRIB_DIVISOR_R == 0 ? 0 : 1) : m_numInstances;
		int gInstances	= m_instancingType == TYPE_ATTRIB_DIVISOR || m_instancingType == TYPE_MIXED	? m_numInstances / ATTRIB_DIVISOR_G + (m_numInstances % ATTRIB_DIVISOR_G == 0 ? 0 : 1) : m_numInstances;
		int bInstances	= m_instancingType == TYPE_ATTRIB_DIVISOR || m_instancingType == TYPE_MIXED	? m_numInstances / ATTRIB_DIVISOR_B + (m_numInstances % ATTRIB_DIVISOR_B == 0 ? 0 : 1) : m_numInstances;

		// Calculate colors.

		float r = (float)clrNdxR / (float)rInstances;
		float g = (float)clrNdxG * 2.0f / (float)gInstances;
		float b = 1.0f - (float)clrNdxB / (float)bInstances;

		// Convert to integer and back if shader inputs are integers.

		if (glu::isDataTypeIntOrIVec(m_rgbAttrType))
		{
			deInt32 intR = (deInt32)(r*FLOAT_INT_SCALE + FLOAT_INT_BIAS);
			deInt32 intG = (deInt32)(g*FLOAT_INT_SCALE + FLOAT_INT_BIAS);
			deInt32 intB = (deInt32)(b*FLOAT_INT_SCALE + FLOAT_INT_BIAS);
			r = ((float)intR - FLOAT_INT_BIAS) / FLOAT_INT_SCALE;
			g = ((float)intG - FLOAT_INT_BIAS) / FLOAT_INT_SCALE;
			b = ((float)intB - FLOAT_INT_BIAS) / FLOAT_INT_SCALE;
		}
		else if(glu::isDataTypeUintOrUVec(m_rgbAttrType))
		{
			deUint32 uintR = (deInt32)(r*FLOAT_UINT_SCALE + FLOAT_UINT_BIAS);
			deUint32 uintG = (deInt32)(g*FLOAT_UINT_SCALE + FLOAT_UINT_BIAS);
			deUint32 uintB = (deInt32)(b*FLOAT_UINT_SCALE + FLOAT_UINT_BIAS);
			r = ((float)uintR - FLOAT_UINT_BIAS) / FLOAT_UINT_SCALE;
			g = ((float)uintG - FLOAT_UINT_BIAS) / FLOAT_UINT_SCALE;
			b = ((float)uintB - FLOAT_UINT_BIAS) / FLOAT_UINT_SCALE;
		}

		// Draw rectangle.

		for (int y = 0; y < hei; y++)
			for (int x = xStart; x < xEnd; x++)
				dst.setPixel(x, y, tcu::RGBA(tcu::Vec4(r, g, b, 1.0f)));
	}
}

InstancedRenderingTests::InstancedRenderingTests (Context& context)
	: TestCaseGroup(context, "instanced", "Instanced rendering tests")
{
}

InstancedRenderingTests::~InstancedRenderingTests (void)
{
}

void InstancedRenderingTests::init (void)
{
	// Cases testing function, instancing method and instance count.

	static const int instanceCounts[] = { 1, 2, 4, 20 };

	for (int function = 0; function < (int)InstancedRenderingCase::FUNCTION_LAST; function++)
	{
		const char* functionName = function == (int)InstancedRenderingCase::FUNCTION_DRAW_ARRAYS_INSTANCED		? "draw_arrays_instanced"
								 : function == (int)InstancedRenderingCase::FUNCTION_DRAW_ELEMENTS_INSTANCED	? "draw_elements_instanced"
								 : DE_NULL;

		const char* functionDesc = function == (int)InstancedRenderingCase::FUNCTION_DRAW_ARRAYS_INSTANCED		? "Use glDrawArraysInstanced()"
								 : function == (int)InstancedRenderingCase::FUNCTION_DRAW_ELEMENTS_INSTANCED	? "Use glDrawElementsInstanced()"
								 : DE_NULL;

		DE_ASSERT(functionName != DE_NULL);
		DE_ASSERT(functionDesc != DE_NULL);

		TestCaseGroup* functionGroup = new TestCaseGroup(m_context, functionName, functionDesc);
		addChild(functionGroup);

		for (int instancingType = 0; instancingType < (int)InstancedRenderingCase::TYPE_LAST; instancingType++)
		{
			const char* instancingTypeName = instancingType == (int)InstancedRenderingCase::TYPE_INSTANCE_ID	? "instance_id"
										   : instancingType == (int)InstancedRenderingCase::TYPE_ATTRIB_DIVISOR	? "attribute_divisor"
										   : instancingType == (int)InstancedRenderingCase::TYPE_MIXED			? "mixed"
										   : DE_NULL;

			const char* instancingTypeDesc = instancingType == (int)InstancedRenderingCase::TYPE_INSTANCE_ID	? "Use gl_InstanceID for instancing"
										   : instancingType == (int)InstancedRenderingCase::TYPE_ATTRIB_DIVISOR	? "Use vertex attribute divisors for instancing"
										   : instancingType == (int)InstancedRenderingCase::TYPE_MIXED			? "Use both gl_InstanceID and vertex attribute divisors for instancing"
										   : DE_NULL;

			DE_ASSERT(instancingTypeName != DE_NULL);
			DE_ASSERT(instancingTypeDesc != DE_NULL);

			TestCaseGroup* instancingTypeGroup = new TestCaseGroup(m_context, instancingTypeName, instancingTypeDesc);
			functionGroup->addChild(instancingTypeGroup);

			for (int countNdx = 0; countNdx < DE_LENGTH_OF_ARRAY(instanceCounts); countNdx++)
			{
				std::string countName = de::toString(instanceCounts[countNdx]) + "_instances";

				instancingTypeGroup->addChild(new InstancedRenderingCase(m_context, countName.c_str(), "",
																		 (InstancedRenderingCase::DrawFunction)function,
																		 (InstancedRenderingCase::InstancingType)instancingType,
																		 glu::TYPE_FLOAT,
																		 instanceCounts[countNdx]));
			}
		}
	}

	// Data type specific cases.

	static const glu::DataType s_testTypes[] =
	{
		glu::TYPE_FLOAT,
		glu::TYPE_FLOAT_VEC2,
		glu::TYPE_FLOAT_VEC3,
		glu::TYPE_FLOAT_VEC4,
		glu::TYPE_FLOAT_MAT2,
		glu::TYPE_FLOAT_MAT2X3,
		glu::TYPE_FLOAT_MAT2X4,
		glu::TYPE_FLOAT_MAT3X2,
		glu::TYPE_FLOAT_MAT3,
		glu::TYPE_FLOAT_MAT3X4,
		glu::TYPE_FLOAT_MAT4X2,
		glu::TYPE_FLOAT_MAT4X3,
		glu::TYPE_FLOAT_MAT4,

		glu::TYPE_INT,
		glu::TYPE_INT_VEC2,
		glu::TYPE_INT_VEC3,
		glu::TYPE_INT_VEC4,

		glu::TYPE_UINT,
		glu::TYPE_UINT_VEC2,
		glu::TYPE_UINT_VEC3,
		glu::TYPE_UINT_VEC4
	};

	const int typeTestNumInstances = 4;

	TestCaseGroup* typesGroup = new TestCaseGroup(m_context, "types", "Tests for instanced attributes of particular data types");
	addChild(typesGroup);

	for (int typeNdx = 0; typeNdx < DE_LENGTH_OF_ARRAY(s_testTypes); typeNdx++)
	{
		glu::DataType type = s_testTypes[typeNdx];

		typesGroup->addChild(new InstancedRenderingCase(m_context, glu::getDataTypeName(type), "",
														InstancedRenderingCase::FUNCTION_DRAW_ARRAYS_INSTANCED,
														InstancedRenderingCase::TYPE_ATTRIB_DIVISOR,
														type,
														typeTestNumInstances));
	}
}

} // Functional
} // gles3
} // deqp
